/*
*  C Implementation: lcd
*
* Description: 
*
*
* Author: Ian Harris <ian@rasputin.bombercommand.org.uk>, (C) 2009
*
* Copyright: See COPYING file that comes with this distribution
*
*/
#include <stdio.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <math.h>
#include <avr/pgmspace.h>
#include <string.h>
#include "lcd.h"
#include "numbers_11x13.h"
#include "utils.h"

/*
** *****************************************************************************
**               L O C A L   F U N C T I O N   P R O T O T Y P E S
** *****************************************************************************
*/
static void LCD_SPI_DBCommand(const unsigned int data[], unsigned int count);
static void LCD_SPI_Int(unsigned int Value);
static void LCD_SPI_Byte(const unsigned char data[], unsigned int count);
static void LCD_wait(void);
static unsigned int greyscaleColor( unsigned char idx );
static unsigned int greyscaleTint( unsigned char idx, unsigned char msk );
static unsigned int WindowWidth(void);
static unsigned int WindowHeight(void);

const unsigned int LCD_Width = 132;
const unsigned int LCD_Height = 176;


EWindowOrientation Orientation = Portrait;


extern const prog_uint8_t Font1[];
extern void delay_ms(uint16_t period);	 //delay routine (milliseconds)




// ##### LCD subroutines below  ######################################################################################


/*
** *****************************************************************************
**                       G L O B A L   F U N C T I O N s
** *****************************************************************************
*/

//-------------------------------------------------------------------------------
// Displays a string
//-------------------------------------------------------------------------------

static inline void putlcdchar( const char ch, unsigned char x, unsigned char y, unsigned int foreColor, unsigned int backColor )
{
	prog_uint8_t by;
	unsigned char msk, iy, ix;

	LCD_Window( x, y, x+4, y+7 );				// set character window
	for( iy = 0; iy < 8; iy++ ) {
		by = pgm_read_byte( &Font1[(ch-32) * 8 + iy] );	//read row information for character
		msk = 0x10;
		for( ix = 0; ix < 5; ix++ ) {
			LCD_SPI_Int( ( by & msk ) ? foreColor : backColor );
			msk = msk >> 1;
		}
	}
	SET(LCD_SELECT);											// Disable display
}

void LCD_Print(const char* text, unsigned char X, unsigned char Y, unsigned int ForeColor, unsigned int BackColor)
{
	while( *text ) {
		putlcdchar( *text, X, Y, ForeColor, BackColor );
		X += 6;
		text++;
	}
}


void LCD_Print_Int( int value, unsigned char len, unsigned char decimals, unsigned char X, unsigned char Y, unsigned int ForeColor, unsigned int BackColor  )
{
	unsigned char i;
	bool forceZeros;

	if( decimals > 0 ) X += 6;	//add in the decimal place
	X += ((len - 1) * 6);		//add in the normal characters
	forceZeros = true;

	while( len-- ) {
		i = value % 10;
		value = value / 10;
		if( (i == 0) && (!forceZeros) && (value == 0) ) {
			putlcdchar( ' ', X, Y, ForeColor, BackColor );
		}
		else {
			putlcdchar( '0' + i, X, Y, ForeColor, BackColor );
		}
		X -= 6;
		forceZeros = false;
		if( decimals >= 1 ) {
			decimals--;
			if( decimals == 0 ) {
				//that was all the decimals
				putlcdchar( '.', X, Y, ForeColor, BackColor );
				X -= 6;			// jump the dot
			}
			forceZeros = true;	// there for the 1's column
		}
	}

}


void LCD_PrintHex(char v, unsigned char X, unsigned char Y, unsigned int ForeColor, unsigned int BackColor)
{
	const char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
	putlcdchar( hexval[ (v >> 4) & 0x0f ], X, Y, ForeColor, BackColor );
	putlcdchar( hexval[ v & 0x0f ], X+6, Y, ForeColor, BackColor );
}



void LCD_Init(void)
{
	DDRB = 0xff;				//all Ports to output
	SPSR        |= _BV(SPI2X);
	SPCR        = _BV (SPE) | _BV(MSTR);// | _BV(CPOL) | _BV(CPHA); 
	
	delay_ms(300); 
	RESET(LCD_RESET);	delay_ms(75);
	SET(LCD_SELECT);	delay_ms(75);
	RESET(LCD_CLK);		delay_ms(75); 
	SET(LCD_DC);		delay_ms(75); 
	SET(LCD_RESET);		delay_ms(75); 
	
	unsigned int init_data[] = 
	{ 
		0xFDFD, 0xFDFD, 
		// pause 
		0xEF00, 0xEE04, 0x1B04, 0xFEFE, 0xFEFE, 
		0xEF90, 0x4A04, 0x7F3F, 0xEE04, 0x4306, 
		// pause 
		0xEF90, 0x0983, 0x0800, 0x0BAF, 0x0A00, 
		0x0500, 0x0600, 0x0700, 0xEF00, 0xEE0C, 
		0xEF90, 0x0080, 0xEFB0, 0x4902, 0xEF00, 
		0x7F01, 0xE181, 0xE202, 0xE276, 0xE183, 
		0x8001, 0xEF90, 0x0000 
	}; 
	LCD_SPI_DBCommand(&init_data[0], 2);	delay_ms(75); 
	LCD_SPI_DBCommand(&init_data[2], 10);	delay_ms(75); 
	LCD_SPI_DBCommand(&init_data[12], 23); 	delay_ms(75);
	RESET(LCD_SELECT); 

	//this sets up PB7 as the brightness control of the backlight
	//driven off Timer0
	TCCR0A = 0x48 | 0x30 | 0x04;		//fast PWM, set OC0A on match, clear at top, 256 prescaler
	LCD_BLIGHT = 0x80;

	
}


//-------------------------------------------------------------------------------
//Backlight control
//-------------------------------------------------------------------------------
void LCD_SetBacklight( uint8_t wLevel )
{
	LCD_BLIGHT = wLevel;
}

//-------------------------------------------------------------------------------
// Displays a string
//-------------------------------------------------------------------------------
static inline uint8_t LCD_Smooth_Number_Char(const char c, unsigned char X, unsigned char Y )
{
	uint8_t r;
	
	if( c == 32 ) {		// space is handled separately as no data is required
		LCD_Window( X, Y, X+11-1, Y+13-1 );
		for( unsigned int i = 0; i<(13*11); i++ ) {
			LCD_SPI_Int( 0 );
		}
		r = 11;
	}
	else if( c == '-' ) {	// minus sign
		LCD_Window( X +1, Y + 7, X + 8, Y + 9 );
		for( unsigned int i = 0; i<24; i++ ) {
			LCD_SPI_Int( greyscaleColor( 128 ) );
		}
		r = 11;
	}
	else if( c == '.' ) {	// decimal place
		LCD_Window( X + 1, Y + 9, X + 3, Y + 12 );	
		for( unsigned int i = 0; i<12; i++ ) {
			LCD_SPI_Int( greyscaleColor( pgm_read_byte( &dot[i] ) ) );
		}
		r =  5;
	}
	else if( c == ':' ) {	// colon
		LCD_Window( X + 1, Y + 4, X + 3, Y + 7 );	
		for( unsigned int i = 0; i<12; i++ ) {
			LCD_SPI_Int( greyscaleColor( pgm_read_byte( &dot[i] ) ) );
		}
		SET( LCD_SELECT );		// Disable display - gets re-enabled by next cmd
		LCD_Window( X + 1, Y + 9, X + 3, Y + 12 );	
		for( unsigned int i = 0; i<12; i++ ) {
			LCD_SPI_Int( greyscaleColor( pgm_read_byte( &dot[i] ) ) );
		}
		r = 5;		
	}
	else {
		LCD_Window( X, Y, X+11-1, Y+13-1 );
		unsigned int pos = (c - '0') * 143;
		for( unsigned int i=0; i<(13*11); i++ ) {
			LCD_SPI_Int( greyscaleColor( pgm_read_byte( &numbers[pos+i] ) ) );
		}
		r = 11;
	}
	SET( LCD_SELECT );		// Disable display

	return r;

}

void LCD_Smooth_Numbers(const char* Text, unsigned char X, unsigned char Y )
{
	while( *Text ) {
		X += LCD_Smooth_Number_Char( *Text, X, Y );
		Text++;
	}
}


void LCD_Smooth_Int( int value, unsigned char len, unsigned char decimals, unsigned char X, unsigned char Y )
{
	unsigned char i;
	bool forceZeros;

	if( decimals > 0 ) X += 5;	//add in the decimal place
	X += ((len - 1) * 11);		//add in the normal characters
	forceZeros = true;
	while( len-- ) {
		i = value % 10;
		value = value / 10;
		if( (i == 0) && (!forceZeros) && (value == 0) ) {
			LCD_Smooth_Number_Char( ' ', X, Y );
		}
		else {
			LCD_Smooth_Number_Char( '0'+ i, X, Y );
		}
		X -= 11;
		forceZeros = false;
		if( decimals >= 1 ) {
			decimals--;
			if( decimals == 0 ) {
				//that was all the decimals
				X -= 5;			// jump the dot
			}
			forceZeros = true;	// there for the 1's column
		}
	}
}
//-------------------------------------------------------------------------------
//Set Output window
//-------------------------------------------------------------------------------
void LCD_Window(int x1, int y1, int x2, int y2) 
{ 
	unsigned char data[] = 
	{ 
		0xEF, 0x08,
		0x18, 0x00,
		0x12, x1,
		0x15, x2,
		0x13, y1,
		0x16, y2 
	};
	switch (Orientation)
	{
		default:
			// Invalid! Fall through to portrait mode
		case Portrait:
			// do nothing, data is intitialized for Portrait mode
			break;
		case Portrait180:
			data[3] = 0x03;		// Mode
			data[5] = LCD_Width-1 - x1;
			data[7] = LCD_Width-1 - x2;
			data[9] = LCD_Height-1 - y1;
			data[11] = LCD_Height-1 - y2;
			break;
		case Landscape:
			data[3] = 0x05;		// Mode
			data[5] = LCD_Width-1 - y1;
			data[7] = LCD_Width-1 - y2;
			data[9] = x1;
			data[11] = x2;
			break;
		case Landscape180:
			data[3] = 0x06;		// Mode
			data[5] = y1;
			data[7] = y2;
			data[9] = LCD_Height-1 - x1;
			data[11] = LCD_Height-1 - x2;
			break;
	}
	SET(LCD_DC);														// switch to command mode as we send a command array, not a data array  
	RESET(LCD_SELECT);             										// enable display
	LCD_SPI_Byte(data, 12); 											// send 12 command bytes
	RESET(LCD_DC);														// disable command mode = data mode on  
} 


//-------------------------------------------------------------------------------
//Clear display
//-------------------------------------------------------------------------------

void LCD_Cls(unsigned int color) 
{ 
	LCD_Window(0, 0, WindowWidth()-1, WindowHeight()-1);
	for (unsigned int i=0; i<(WindowWidth()*WindowHeight()); ++i) 
		LCD_SPI_Int(color); 
	SET(LCD_SELECT);													// 'Disable display 
} 

//-------------------------------------------------------------------------------
//Plot one pixel to the display
//-------------------------------------------------------------------------------
void LCD_Plot(unsigned char x1, unsigned char y1, unsigned char line_type, unsigned int color)
{
	if (line_type == THICK)
	{
		LCD_Window(x1, y1, x1+1, y1+1); 									// Define Window, exactly four pixels large
		LCD_SPI_Int(color);													// send four pixels (avoid loop overhead)
		LCD_SPI_Int(color);
		LCD_SPI_Int(color);
		LCD_SPI_Int(color);
	}
	else
	{
		LCD_Window(x1, y1, x1, y1); 										// Define Window, exactly one pixel large
		LCD_SPI_Int(color);													// send pixel
	}
	SET(LCD_SELECT);													// Disable Display
}

//-------------------------------------------------------------------------------
//Draws a rectangular filled box
//-------------------------------------------------------------------------------
void LCD_Box(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned int color)
{
	LCD_Window(x1, y1, x2, y2);   										// Define Window
	for (unsigned int i=0; i<((x2-x1+1)*(y2-y1+1)); ++i)			// for every pixel...
		LCD_SPI_Int(color); 											// ...send color information
	SET(LCD_SELECT);													// Disable Display
}

//-------------------------------------------------------------------------------
//Draws a rectangular (not filled)
//-------------------------------------------------------------------------------
void LCD_Rect(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned char line_type, unsigned int color)	//draw rectangle
{
      LCD_Draw(x1, y1, x2, y1, line_type, color);
      LCD_Draw(x2, y1, x2, y2, line_type, color);
      LCD_Draw(x1, y2, x2, y2, line_type, color);
      LCD_Draw(x1, y1, x1, y2, line_type, color);
}

//-------------------------------------------------------------------------------
//Draws a line
//-------------------------------------------------------------------------------
void LCD_Draw(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned char line_type, unsigned int color)//draw line - bresenham algorithm
{
   int x = x1;
   int y = y1;
   int d = 0;
   int hx = x2 - x1;    									// how many pixels on each axis
   int hy = y2 - y1;
   int xinc = 1;
   int yinc = 1;
   if (hx < 0)
   {
      xinc = -1;
      hx = -hx;
   }
   if (hy < 0)
   {
      yinc = -1;
      hy = -hy;
   }
   if (hy <= hx)
   {
      int c = 2 * hx;
      int m = 2 * hy;
      while (x != x2)
	  {
         LCD_Plot(x, y, line_type, color);
         x += xinc;
         d += m;
         if (d > hx)
		 {
            y += yinc;
            d -= c;
         }
      }
   }
   else
   {
      int c = 2 * hy;
      int m = 2 * hx;
      while (y != y2)
	  {
         LCD_Plot(x, y, line_type, color);
         y += yinc;
         d += m;
         if (d > hy)
		 {
            x += xinc;
            d -= c;
         }
      }
   }
   LCD_Plot(x, y, line_type, color); 												  // finally, the last pixel
 }

//-------------------------------------------------------------------------------
//Draws an unfilled circle
//-------------------------------------------------------------------------------
void LCD_CirclePoints(int x, int y, int xOfs, int yOfs, unsigned char line_type, unsigned int color)
{
	LCD_Plot(xOfs+x, yOfs+y, line_type, color);
	LCD_Plot(xOfs+y, yOfs+x, line_type, color);
	LCD_Plot(xOfs+y, yOfs-x, line_type, color);
	LCD_Plot(xOfs+x, yOfs-y, line_type, color);
	LCD_Plot(xOfs-x, yOfs-y, line_type, color);
	LCD_Plot(xOfs-y, yOfs-x, line_type, color);
	LCD_Plot(xOfs-y, yOfs+x, line_type, color);
	LCD_Plot(xOfs-x, yOfs+y, line_type, color);
}


//-------------------------------------------------------------------------------
//Draws a filled circle
//-------------------------------------------------------------------------------
void LCD_Circle(int x1, int y1, int r, unsigned char fill, unsigned char line_type, unsigned int color)	//draw circle
{
	int x=0, y=r, d=1-r, deltaE=3, deltaSE=-2*r+5;
	LCD_CirclePoints(x, y, x1, y1, line_type, color);
	if	(fill)
		LCD_Draw(x1-y, y1-x, x1+y, y1-x, line_type, color);
	while (y > x)
	{
		if (d < 0)	// select E
		{
			d += deltaE;
			deltaE += 2;
			deltaSE += 2;
			++x;
		}
		else       // select SE
		{
			d += deltaSE;
			deltaE += 2;
			deltaSE += 4;
			++x; --y;
		}
		if (fill)
		{
			LCD_Box(x1-x, y1+y, x1+x, y1+y, color);			
			LCD_Box(x1-x ,y1-y, x1+x, y1-y, color);
			LCD_Box(x1-y, y1+x, x1+y, y1+x, color);
			LCD_Box(x1-y, y1-x, x1+y, y1-x, color);
		}
		else
		{
			LCD_CirclePoints(x, y, x1, y1, line_type, color);
		}
	}
}

//-------------------------------------------------------------------------------
//send data for a whole bitmap
//-------------------------------------------------------------------------------
void LCD_Bitmap(int x1, int y1, const prog_uint8_t pbmp[] )
{
	unsigned int w, h;

	w = pgm_read_byte( &pbmp[1] );
	h = pgm_read_byte( &pbmp[2] );

	LCD_BitmapWindow( x1, y1, 0, 0, w, h, pbmp );
}


//-------------------------------------------------------------------------------
//send data for a windowed bitmap
//-------------------------------------------------------------------------------
void LCD_BitmapWindow(int x1, int y1, int bmx, int bmy, int bmw, int bmh, const prog_uint8_t pbmp[] )
{
	unsigned int w, h, c;
	unsigned int x, y;
	unsigned int ofs;
	unsigned char fmt;
	unsigned char b;

	fmt = pgm_read_byte( &pbmp[0] );
	w = pgm_read_byte( &pbmp[1] );
	h = pgm_read_byte( &pbmp[2] );

	LCD_Window( x1, y1, x1 + bmw - 1, y1 + bmh - 1 );    // Define Window

	if( fmt == 128 ) {
		//greyscale
		ofs = 3 + bmx + (bmy * w);
		for( y = 0; y < bmh; y++ ) {
			for( x = 0; x < bmw; x++ ) {
				LCD_SPI_Int( greyscaleColor( pgm_read_byte( &pbmp[ofs + x]) ));
			}
			ofs += w;	//width of IMAGE, not view window
		}
	}
	else {
		switch(fmt) {
			case 0: ofs = 3 + (8*2); break;
			case 1: ofs = 3 + (64*2); break;
			case 2: ofs = 3 + (256*2); break;
			default: ofs = 3;
		}
		ofs += bmx + (bmy * w);
		for( y = 0; y < bmh; y++ ) {
			for( x = 0; x < bmw; x++ ) {
				b = pgm_read_byte( &pbmp[ofs + x] );
				c = b;
				c = (c * 2) + 3;
				LCD_SPI_Int( pgm_read_word( &pbmp[c] ) );
			}
			ofs += w;	//width of IMAGE, not view window
		}
	}

	SET(LCD_SELECT);				 // Disable display
}


//-------------------------------------------------------------------------------
//send data for a windowed bitmap
//-------------------------------------------------------------------------------
void LCD_BitmapWindowTint(int x1, int y1, int bmx, int bmy, int bmw, int bmh, const prog_uint8_t pbmp[], char msk )
{
	unsigned int w, h;
	unsigned int x, y;
	unsigned int ofs;
	unsigned char fmt;

	fmt = pgm_read_byte( &pbmp[0] );
	w = pgm_read_byte( &pbmp[1] );
	h = pgm_read_byte( &pbmp[2] );

	if( fmt == 128 ) {

		LCD_Window( x1, y1, x1 + bmw - 1, y1 + bmh - 1 );    // Define Window

		//only works with greyscales
		ofs = 3 + bmx + (bmy * w);
		for( y = 0; y < bmh; y++ ) {
			for( x = 0; x < bmw; x++ ) {
				LCD_SPI_Int( greyscaleTint( pgm_read_byte( &pbmp[ofs + x] ), msk ));
			}
			ofs += w;	//width of IMAGE, not view window
		}
		SET(LCD_SELECT);				 // Disable display
	}

}


//##### SPI START  ##############################################################################################
//   if you do not want to use hardware SPI but software SPI (e.g. because your controller does not offer hardware SPI,
//   exchange the called subroutines:
//   1): LCD_SPI_Byte with subroutine LCD_Send_Byte  AND....
//   2): LCD_SPI_DBCommand with LCD_Send_DBCommand
//   But it should be mentioned, that hardware SPI is up to 10 times faster than software SPI!

// send single bytes (or array with Bytes) by SPI 
static inline void LCD_SPI_Byte(const unsigned char data[], unsigned int count)
{ 
	for( int i = 0; i < count; ++i ) {
		SPCR |= _BV(SPE);
		SPDR = data[i];
		LCD_wait();
	} 
}


// send word (16 bit) (or array with words) by SPI 
void LCD_SPI_DBCommand(const unsigned int data[], unsigned int count)
{
	for( int i = 0; i < count; ++i ) {
		SET(LCD_DC);						// Selects Command mode
		RESET(LCD_SELECT);					// Enable display
		unsigned char msbyte = (data[i] >> 8) & 0xff;		// msb!
		unsigned char lsbyte = data[i] & 0xff;			// lsb!	
		SPCR |= _BV(SPE); 					//SPI: Ready
		SPDR = msbyte;						//send first 8 bits
		LCD_wait();							//Wait until all bits has been sent
		SPCR |= _BV(SPE);
		SPDR = lsbyte;						//send last 8 bits
		LCD_wait();
		RESET(LCD_DC);						// Selects Data mode
		SET(LCD_SELECT);					// Disable display
	}
}

// send int by SPI 
static inline void LCD_SPI_Int(unsigned int Value)
{ 
	SPCR |= _BV(SPE);
	SPDR = (Value >> 8) & 0xff;
	LCD_wait();
	SPCR |= _BV(SPE);
	SPDR = Value & 0xff;
	LCD_wait();
}

static inline void LCD_wait(void)
{						//waits for SPI transfer is done
    while (SPCR & _BV(SPE)) {
        while (!(SPSR & (_BV(SPIF))));
        SPCR &= ~(_BV(SPE));
    }
}

static inline unsigned int greyscaleColor( unsigned char idx )
{
	unsigned int c;
	c = idx >> 3;
	c |= (( idx >> 2 ) & 0x3F) << 5;
	c |= (( idx >> 3 ) & 0x1F) << 11;
	return c;
}

static inline unsigned int greyscaleTint( unsigned char idx, unsigned char msk )
{
	unsigned int c = 0;
	if( msk & 0x01 ) c |= (idx >> 3);
	if( msk & 0x02 ) c |= (( idx >> 2 ) & 0x3F) << 5;
	if( msk & 0x04 ) c |= (( idx >> 3 ) & 0x1F) << 11;
	return c;
}

static inline unsigned int WindowWidth(void)
{
	if (Orientation==Portrait || Orientation==Portrait180)
		return LCD_Width;
	else
		return LCD_Height;
}

static inline unsigned int WindowHeight(void)
{
	if (Orientation==Portrait || Orientation==Portrait180)
		return LCD_Height;
	else
		return LCD_Width;
}
